/*
    node 的 mySql  使用 (数据库)

    速查表
    https://www.runoob.com/nodejs/nodejs-express-framework.html   //Express
    https://www.runoob.com/nodejs/nodejs-mysql.html   //连接 MySQL
    https://www.runoob.com/sql/sql-tutorial.html    //写SQL

    功能需求：
    1、快速生成对应所有数据库表的基础API，API名等于表名，实现直接API增删改查表
    2、特殊API按功能开发，如登陆、上传、富文本编辑器
*/
// 基准路径 http://localhost/api/v1/

//建单表基准sql
// CREATE TABLE `demo` (
//   `id` TINYINT(4) NOT NULL AUTO_INCREMENT,
//   `title` VARCHAR(200) DEFAULT NULL,
//   `date` VARCHAR(50) DEFAULT NULL,
//   `value` TEXT,
//   `img` VARCHAR(200) DEFAULT NULL,
//   `images` VARCHAR(600) DEFAULT NULL,
//   `video` VARCHAR(200) DEFAULT NULL,
//   `child` VARCHAR(600) DEFAULT NULL,
//   `sort` INT(5) DEFAULT NULL,
//   `issue` INT(5) DEFAULT NULL,
//   `top` INT(5) DEFAULT NULL,
//   PRIMARY KEY (`id`)
// ) ENGINE=MYISAM AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

//Web应用框架
const express = require('express')
const app = express() //实例化express应用
app.use('/public/serves/images', express.static('public')) // 设置图片、视频、附件等静态资源存放路径
const bodyParser = require('body-parser') // post中间件，用于post解析参数
// 处理post请求数据解析
app.use(bodyParser.json()) // JSON请求
app.use(bodyParser.urlencoded({extended: false})) // 表单请求

//文件系统
const fs = require('fs') 

// 在处理模块中引入第三方解析模块
const multipart = require('connect-multiparty') 
const multipartMiddleware = multipart() // post数据解析

// cors跨域支持
const cors = require('cors') 
app.use(cors())

//mysql查询封装
const {query,sqlConfig} = require('./API/mysql')

//自定义工具函数
const {objectToSqlString,System,thisDate,verifyUserData} = require('./API/utils')

// API版本号
const versions = '/v1/'

//存储数据库所有表名，用于生成批量同名API增删改查，实现API同名操作数据库表
let apiArray = [] //['table_name','table_name']

//读取数据库所有表名（查询数据库）
async function initTable(){
   const data = await query("SELECT table_name FROM information_schema.tables WHERE table_schema='" + sqlConfig.database + "'");    
   apiArray = JSON.parse(JSON.stringify(data)).map(function(value){
      return value.table_name
   })   
}
  
/* 生成所有数据表常规增删改查API */
initTable().then(function(){ //初始化表回调
  // 批量生成增删改查API
  apiArray.forEach((item) => {
    getColumnNameAPI(item) //初始化列名查询
    getAPI(item) //查
    deleteAPI(item) // 删    
    if (item !== 'login' || item !== 'table_login'){ // 登录用户数据单独处理
      putAPI(item) // 改
      addAPI(item) // 增
    }     
  })  
})

/* 数据表列名(数据接口) */
function getColumnNameAPI(item){
  app.get(versions + item + 'Head',async function (req, res) { // 建立数据接口
    const rows = await query('SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_NAME="' + item + '"') //列名查询
    res.json(rows)
  })
}
// 添加（数据接口）
function addAPI(item) {
  app.post(versions + item,async function (req, res) { // 建立数据接口
    let da = req.body // 请求体
    let te = '' //查询字段
    let valTe = '' //对应值
    da = objectToSqlString(da); //格式化请求参数value值
    for (let key in da) { // sql字段拼接
      if(key === 'id') continue; //ID不参与拼接(ID在数据库设置自增长)
      te += ',' + key //拼接key
      valTe += ',' + da[key] //拼接value
    }   
    te = te.slice(1)
    valTe = valTe.slice(1) //去掉第一个','
    // 'INSERT INTO oplog(username,text,date)  VALUES('admin','新增内容：主力店（成功）','2021-11-05 11:03:50')'
    const result = await query('INSERT INTO ' + item + '(' + te + ')  VALUES(' + valTe + ')')
    if(item === 'roles'){ //roles表关联login表添加用户数据
      da.rid = JSON.parse(JSON.stringify(result)).insertId //记录自增ID
      await query('INSERT INTO login(username,password,date,rid) VALUES(' + da.username + ',' + da.password + ',' + da.date + ',' + da.rid + ')')
    }
    res.status(200).json()
  })
}
// 修改内容（数据接口,id为必需）
function putAPI(item) {
  app.put(versions + item,async function (req, res) { // 建立数据接口
    let da = req.body // 请求体 数据案例 { top: false, id: [ 18, 16 ] }  || { top: false, id: 16 }
    const idUl = Array.isArray(da.id) ? da.id.join(",") : da.id //多个ID则用','合并ID
    let te = '';
    da = objectToSqlString(da); //格式化请求参数value值
    for(let key in da){    
      if(key === 'id') continue; //ID不参与拼接   
      te += "," + key + "=" + da[key] //拼接sql查询字段
    }
    te = te.slice(1) //去掉第一个','
    //UPDATE websites SET issue=1  WHERE id IN (19,15,16)  批量更新sql
    //UPDATE floor SET title="L21",sort=10,issue=1,top=0,date="2021-06-23 17:36:52" WHERE id IN (18) 多字段更新
    await query('UPDATE ' + item + ' SET ' + te + ' WHERE id IN (' + idUl + ")")
    if(item === 'roles'){ //roles表关联login表修改用户数据
      await query('UPDATE login SET username=' + da.username + ',password=' + da.password + ',date=' + da.date + ' WHERE rid IN (' + da.id + ')')
    }
    res.status(200).json()
  })
}
// 删除（数据接口）
function deleteAPI(item) {
  app.delete(versions + item,async function (req, res) { // 建立数据接口
    const da = req.query // 请求数据
    // DELETE FROM websites where id=6
    const result = await query('DELETE FROM ' + item + ' WHERE id= ' + da.id)
    if(item === 'roles'){ //roles表关联login表删除用户数据
      da.rid = JSON.parse(JSON.stringify(result)).insertId //记录自增ID
      await query('DELETE FROM login WHERE rid= ' + da.id)
    }
    res.status(200).json()
  })
}

//自定义get数据处理函数
const {stringToArray,filterIssue,keywordSearch,filterType,pagaData,idData,idLinkData,classData,dataSize,sortData} = require('./API/gets')

// get数据（数据接口）
function getAPI(item){
  // 建立数据接口
  app.get(versions + item,async function (req, res) { 
    //请求API指定表数据
    let apiTableData = await query('SELECT * FROM ' + item)

    //请求API返回的数据，这里用了深拷贝，以隔绝对数据库数据的影响
    let data = JSON.parse(JSON.stringify(apiTableData));
    /* 数组字符串转数组 */
    data = stringToArray(data)     
    //筛选发布数据(all=1)
    data = filterIssue(data,req)    
    //搜索关键字(keyword=?)
    data = keywordSearch(data,req)
    //筛选分类(type=?)
    data = filterType(data,req)
    //数据分页 (pagenum=?,pagesize=?) (返回：{list:[],sum:?})
    data = pagaData(data,req)
    //指定ID访问单条数据(id=?)
    data = idData(data,req)
    //返回多条ID相连数据(id=?,linksize=?)（返回：{list:[]}）
    data = idLinkData(data,apiTableData,req)
    //指定分类(type=?)
    data = classData(data,req)
    //限制数据条数
    data = dataSize(data,req)
    //数据排序
    data = sortData(data)
    res.json(data) // 响应头返回相应查询数据
  })
}

// 系统信息API
app.get(versions + 'system', function (req, res) { // 返回系统信息
  res.json(new(System)() ) //系统信息为自定义的Class
})

// 用户登录数据接口(req.params.id(get请求参数 /:id)  req.query.id(get请求 ?id=0)   req.body.id(post请求体))
app.post(versions + 'login',async function (req, res) { // 建立数据接口
  const loginData = await query('SELECT * FROM login') //登陆数据
  res.json(verifyUserData(req.body,loginData)) // 以json形式发送响应数据
})

// 修改密码数据接口
app.put(versions + 'login',async function (req, res) { // 建立数据接口
  let reqBody = req.body // post请求数据
  const loginData = await query('SELECT * FROM login') //登陆数据
  const resData = verifyUserData(reqBody,loginData); //返回响应数据
  if(resData.meta.status === 200){//为成功响应执行修改密码操作
    resData.meta.message = "密码修改成功"
    reqBody = objectToSqlString(reqBody); //格式化请求数据
    const {setPassword,username} = reqBody
    await query('UPDATE login SET password=' + setPassword + ' WHERE username IN (' + username + ')') //修改登陆密码
    await query('UPDATE roles SET password=' + setPassword + ' WHERE username IN (' + username + ')') //关联修改角色表用户密码
  }
  res.json(resData) // 以json形式发送响应数据
})

/* neditor编辑器 */
let buf = new Buffer.alloc(3000)
fs.open(__dirname + '/ueConfig.json', 'r+', function (error, fd) { // 打开ueditor配置文件
  if (error) throw error;
  // console.log("配置文件打开成功");
  fs.read(fd, buf, 0, buf.length, 0, function (error, bytes) { // 读取配置
    if (error) throw error;
    // console.log(bytes + "  字节被读取");
    app.get(versions + 'ueditor', function (req, res) { // 文件上传
      const daOb = JSON.parse(buf.slice(0, bytes).toString())
      res.jsonp(daOb) // 传送JSONP响应
    })
  })
})

/* element文件上传 */
let response
app.post(versions + 'file_upload', multipartMiddleware, function (req, res) { // 文件上传
  //console.log(req.files.file) // 上传文件信息
  const des_file = '/images/' + thisDate() + req.files.file.originalFilename // 文件存放相对路径
  fs.readFile(req.files.file.path, function (error, data) {
    fs.writeFile(__dirname + des_file, data, function (error) { // _dirname （写入需绝对路径，把相对路径转换成绝对路径）
      if (error) throw error;
      response = {
        message: '文件上传成功',
        data: '/serves'+des_file
      }      
      //console.log(response)
      res.json(response)
    })
  })
})

// 编辑器文件上传
app.post(versions + 'ueditor', multipartMiddleware, function (req, res) { 
  // const action = req.query.action;
  // if (action == 'uploadimage' || action == 'uploadvideo' || action == 'uploadfile') { //图片/视频/附件上传判断
  const des_file = '/images/' + thisDate() + req.files.upfile.originalFilename // 文件存放相对路径
  fs.readFile(req.files.upfile.path, function (error, data) {
    fs.writeFile(__dirname + des_file, data, function (error) { // _dirname （写入需绝对路径，把相对路径转换成绝对路径）
      if (error) throw error;
      let responseImg = { //要按neditor后端请求规范返回响应数据
        code: '200',
        message: 'SUCCESS',
        url: '/serves'+des_file,
        thumbnail: '/serves'+des_file,
        title: req.files.upfile.originalFilename,
        original: req.files.upfile.originalFilename,
        error: error
      }
      res.jsonp(responseImg)
    })
  })
})

//统一的异常处理，防止程序崩溃
process.on('uncaughtException', function (err) { 
  //打印出错误
  console.log(err) 
});

// 配置服务端口
app.listen(5009, () => {
  console.log('server :localhost:5009/v1/')
})